﻿namespace Refract.Composition
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.Linq;

    using Refract.MVVM;

    /// <summary>
    /// Implements functionality for dealing with composition modules
    /// </summary>
    public class ModuleService : IModuleService
    {
        #region Fields

        [ImportMany(AllowRecomposition = true)]
        public IModuleController[] ModuleControllerList;

        private Dictionary<String, bool> _catalogList = new Dictionary<string, bool>();

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ModuleService"/> class.
        /// </summary>
        public ModuleService()
        {
            AggregateCatalog = new AggregateCatalog(new DeploymentCatalog());

            var container = new CompositionContainer(AggregateCatalog);

            CompositionHost.Initialize(container);

            container.ComposeExportedValue<IModuleService>(this);
            container.ComposeExportedValue(container);

            Container.Current = container;
            CompositionInitializer.SatisfyImports(this);
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets or sets the aggregate catalog.
        /// </summary>
        /// <value>The aggregate catalog.</value>
        private AggregateCatalog AggregateCatalog
        {
            get; set;
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Loads a module and adds composable parts to the catalog
        /// </summary>
        /// <param name="moduleName">Name of the module.</param>
        /// <param name="moduleReadyCallback">The module ready callback.</param>
        public void AddModule(string moduleName, Action moduleReadyCallback = null)
        {
            var evt = new EventHandler<AsyncCompletedEventArgs>((s, e) => DownloadCompleted(moduleName, moduleReadyCallback, e));

            var newCatalog = new DeploymentCatalog(new Uri(moduleName, UriKind.Relative));

            newCatalog.DownloadCompleted += evt;
            newCatalog.DownloadAsync();

            AggregateCatalog.Catalogs.Add(newCatalog);
        }

        /// <summary>
        /// Ensures that a module is loaded by loading it if it hasn't already.
        /// </summary>
        /// <param name="moduleName">Name of the module.</param>
        /// <param name="moduleReadyCallback">The module ready callback.</param>
        public void EnsureModuleIsLoaded(string moduleName, Action moduleReadyCallback)
        {
            if (_catalogList.ContainsKey(moduleName) && _catalogList[moduleName] == true)
            {
                moduleReadyCallback();
            }
            else
            {
                AddModule(moduleName, moduleReadyCallback);
            }
        }

        /// <summary>
        /// Gets a controller.
        /// </summary>
        /// <param name="controllerName">Name of the controller.</param>
        /// <returns></returns>
        public IModuleController GetController(string controllerName)
        {
            var query = from controller in ModuleControllerList
                        where controller.GetType().Name == controllerName
                        select controller;

            return query.SingleOrDefault();
        }

        /// <summary>
        /// Executed when a downloads is completed.
        /// </summary>
        /// <param name="moduleName">Name of the module.</param>
        /// <param name="moduleReadyCallback">The module ready callback.</param>
        /// <param name="eventArgs">The <see cref="System.ComponentModel.AsyncCompletedEventArgs"/> instance containing the event data.</param>
        private void DownloadCompleted(string moduleName, Action moduleReadyCallback, AsyncCompletedEventArgs eventArgs)
        {
            if (eventArgs.Error != null) throw eventArgs.Error;

            if (eventArgs.Cancelled) return;

            _catalogList[moduleName] = true;
            if (moduleReadyCallback != null) moduleReadyCallback();
        }

        #endregion Methods
    }
}